# Docker

 Чтобы запустить приложение в контейнере Docker, вам прежде всего понадобится создать образ Docker используя `Dockerfile`. Поэтому, создадим два `Dockerfile`, по одному для каждой директории проекта.

 ## Docker образы
 
 Чтобы получить необходимые зависимости, нам нужно выполнить следующий скрипт:
 ```
 coffee-shop/liberty/prepare.sh
 barista/liberty/prepare.sh
 ```
 Давайте начнем с приложения coffee-shop. В каталоге `coffee-shop/` мы создадим `Dockerfile` со следующим содержимым:
 ```
 FROM open-liberty:20.0.0.3-full-java8-openj9

 COPY liberty/extension /liberty/usr/extension/
 COPY liberty/server.xml /config/

 COPY target/coffee-shop.war /config/dropins/
 ```
 Имя образа после `FROM` определяет базовый образ для нашего приложения. Используем это в качестве отправной точки. Этот базовый образ уже включает Open Liberty - сервер приложений и среду выполнения Java.

 `/config` и `config/dropins` - директории внутри образа, где мы можем размещать наши конфигурации и деплоить артефакты.

 Команда `COPY` копирует ресурсы с нашего хоста в образ контейнера. Файл `liberty/server.xml` содержит конфигурацию сервера для конкретного приложения, включая все необходимые особенности, которые мы хотим использовать в нашем сервисе. Поскольку мы деплоим по одному приложению на контейнер, конфигурация сервера используется для каждого приложения индивидуально.

 Потратьте минуту и изучите файл `liberty/server.xml`. Мы вернемся к нему несколько позже.

 Теперь, создадим `Dockerfile` для приложения `barista` в разделе `barista/`:

 ```
 FROM open-liberty:20.0.0.3-full-java8-openj9

 COPY liberty/extension /liberty/usr/extension/
 COPY liberty/server.xml /config/

 COPY target/barista.war /config/dropins/
 ```

 ## Сборка

 Чтобы создать образы, используем следующую команду Docker: `docker build -t <image>:<tag> .` **(включая точку в конце!)**

 Для приложения `coffee-shop` это будет выглядеть следующим образом:
 ```
 cd coffee-shop/
 docker build -t de.icr.io/cee-<your-name>-workshop/coffee-shop:1 .
 ```
 Имя образа, в данном случае `de.icr.io/cee-<your-name>-workshop/coffee-shop:1`, содержит адрес реестра образов `de.icr.io/cee-<your-name>-workshop`, само имя образа `coffee-shop` и версию `1`(или тег). Если образ скачивается из реестра (`pull`) или загружается в реестр (`push`), то Docker уже будет знать пункт назначения.

 Часть `de.icr.io/…` указывает использованный облачный сервис, имя пользователя и рабочее пространство.

 Теперь повторяем то же самое для приложения `barista`:

 ```
 cd ../barista/
 docker build -t de.icr.io/cee-<your-name>-workshop/barista:1 .
 ```

 Отлично! Теперь у нас есть два образа для приложений `coffee-shop` и `barista`, которые мы можем запустить как Docker контейнеры.

 ## Запуск

 При желании, мы можем запустить созданные образы Docker локально у себя на хосте.

 Для этого, вам необходимо помнить, что приложение `coffee-shop` работает в связке с бэк-едном `barista`, поэтому необходимо организовать взаимодействие между контейнерами. Если взглянуть на исходный код приложения `coffee-shop`, в частности на шлюз `com.sebastian_daschner.coffee_shop.control.Barista`, то увидем, что приложение обращается к `http: // barista: 9080`, где `barista` является хостом.

 Взаимодействие приложений будет обеспечиваться облачной платформой по управлению контейнерами, которой мы будем управлять независимо от приложений. На уровне платформы наши приложения будут доступны под именами `coffee-shop` и `barista`.

 А для запуска и взаимодействия контейнеров в локальном окружении, мы должны каждому назначить имя и объединить их сетью Docker `dkrnet`:
 ```
 docker network create dkrnet

 docker run --rm -d \
   --name barista \
   --network dkrnet \
   de.icr.io/cee-<your-name>-workshop/barista:1

 docker run --rm -d \
   --name coffee-shop \
   --network dkrnet \
   -p 9080:9080 \
   de.icr.io/cee-<your-name>-workshop/coffee-shop:1
 ```
Эти команды запустят приложения в виде контейнеров Docker. Параметр `--rm` удалит контейнеры после их остановки, `-d` запустит контейнеры в фоновом режиме, `--name` назначет логическое имя контейнеру, `--network` гарантирует, что контейнеры будут работать в указаной сети, а `-p `смапит внутренние порты контейнеров на локальный хост.

В итоге мы получили два работающих контейнера которые могут обращаться друг к другу по логическому имени, что позволяет нашему приложению `coffee-shop` подключаться к бэкэнду `barista`. 

### Вызов приложения

Теперь мы можем получить доступ и простестировать запущенные микросервисы. Поскольку они коммуницируют через HTTP, можем использовать любой REST клиент на ваш выбор, например `curl`.

```
curl localhost:9080/coffee-shop/resources/orders -i
```


### Тест на жизнеспособность
 
 Для того чтобы проверить запущено и работает ли наше приложение мы могли бы просто вызвать его функционал. Но вызов функционала может привести к изменению данных приложения или его состояния. Правильный способ, который практикуется в облачных средах, это использование специально созданного для этих целей Health Check функционала приложения. Мы можем реализовать его, используя простые REST ресурсы, например, реализованные в JAX-RS, а можно использовать готовую спецификацию MicroProfile Health для проверки работоспособности.

 Создадим класс под названием `HealthResource` в проектах `barista` и `coffee shop`:
 ``` JAVA
 import org.eclipse.microprofile.health.*;
 import javax.enterprise.context.ApplicationScoped;

 @Readiness
 @ApplicationScoped
 public class HealthResource implements HealthCheck {

     @Override
     public HealthCheckResponse call() {
         return HealthCheckResponse.named("barista").withData("barista", "ok").up().build();
     }
 }
 ```
 Аналогично создайте класс для `coffee-shop`.

 Готово! 

 Однако, если мы хотим, чтобы эти изменения были включены в наши контейнеры, нам, конечно же, нужно пересобрать приложения и образы Docker.

 После запуска приложений мы сможем получить доступ к `coffee-shop` через локальный порт `9081` и стандартный MicroProfile Health ресурс:
 ```
 curl localhost:9080/health -i
 ```
 Этот запрос обращается к ресурсу проверки работоспособности и, при отсуствии каких-либо ошибок, возвращает HTTP ответ о успешной проверке. Флаг -`i` позволит отображать заголовки HTTP ответа.

 ### Заказ кофе
Теперь мы можем запросить список заказов кофе:
```
curl localhost:9080/coffee-shop/resources/orders
```

 Этот запрос  выдаст нам спискок заказов в виде набора JSON файлов. Поскольку мы еще не создавали заказов то возвращенный массив будет пуст.

 Давайте изменим это и создадим запись о заказе кофе в системе.

 Если мы посмотрим на ресурс JAX-RS в приложении `coffee-shop`, то увидим, что для создания нового заказа на кофе нам нужно запостить (`POST`) JSON, содержащий тип кофе. Используя curl, это действие будет выглядеть следующим образом:
 ```
 curl localhost:9080/coffee-shop/resources/orders -i -XPOST \
  -H 'Content-Type: application/json' \
  -d '{"type":"Espresso"}'
 ```
 `-XPOST` обозначает метод `POST` в HTTP протоколе, `-H` - HTTP заголовок, нужен для того, чтобы сервис определил, что получил на вход JSON файл, а `-d` - указывает на данные, которые мы отправляем как тело HTTP запроса. Отправляя данный запрос, мы расчитываем получить ответ  `201 Created`, сообщающий об успешном внесении заказа в систему.

 Мы можем перепроверить это, снова запросив сведения по заказам кофе, аналогично предыдущему. Однако теперь возвращенный массив должен содержать JSON файл, с информацией о нашем заказе.

 Если это так, то поздравляем! Вы только что создали, запустили и вручную протестировали облачные микросервисы, работающие в контейнерах Docker.

 ## Загрузка образов в реестр
 
 Чтобы сделать наши образы Docker доступными не только локально, запушим их в реестр. Позже мы сможем скачивать их из любой среды, например, из кластера Kubernetes.

 Чтобы загрузить образы, используйте следующие команды:
 ```
 docker push de.icr.io/cee-<your-name>-workshop/coffee-shop:1
 
 docker push de.icr.io/cee-<your-name>-workshop/barista:1
 ```
 Вы можете заметить, что второй команда `push` выполнилась гораздо быстрее и выдала сообщение о том, что практически все слои образа уже существуют в реестре. Это связано с файловой системой copy-on-write, которая используется внтури Docker, и существенно экономит время не только при загрузке, но и при пересборке образов Docker. Docker распознает, какую команду сборки необходимо перевыполнить и выполняет только ее и последующие за ней.

 Именно поэтому, особенно для облачных приложений, имеет смысл создавать небольшие деплоймент артефакты. WAR файлы, которые представляют наши приложения, содержат только бизнес логику приложения. А базовый образ, т.е. JVM, сервер приложений или его конфигурация не меняются так часто, поэтому мы в основном пересобираем и загружаем только бизнес логику приложения.

 Теперь, когда наши микросервисы запущены в контейнерах Docker, приступим к настройке Kubernetes в [следующем разделе](03-kubernetes.md).